/* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

/* variable declarations are in sys_vars.cc now !!! */

#include "my_global.h"                          /* NO_EMBEDDED_ACCESS_CHECKS */
#include "sql_class.h"                   // set_var.h: session_var_ptr
#include "set_var.h"
#include "sql_priv.h"
#include "unireg.h"
#include "mysqld.h"                             // lc_messages_dir
#include "sys_vars_shared.h"
#include "sql_locale.h"                         // my_locale_by_number,
                                                // my_locale_by_name
#include "strfunc.h"      // find_set_from_flags, find_set
#include "sql_parse.h"    // check_global_access
#include "sql_table.h"  // reassign_keycache_tables
#include "sql_time.h"   // date_time_format_copy,
                        // date_time_format_make
#include "derror.h"
#include "sql_acl.h"    // SUPER_ACL
#include "sql_show.h"   // make_default_log_name, append_identifier
#include "sql_view.h"   // updatable_views_with_limit_typelib

static HASH system_variable_hash;
static PolyLock_mutex PLock_global_system_variables(&LOCK_global_system_variables);

/**
  Return variable name and length for hashing of variables.
*/

static uchar *get_sys_var_length(const sys_var *var, size_t *length,
                                 my_bool first)
{
  *length= var->name.length;
  return (uchar*) var->name.str;
}

sys_var_chain all_sys_vars = { NULL, NULL };

int sys_var_init()
{
  DBUG_ENTER("sys_var_init");

  /* Must be already initialized. */
  DBUG_ASSERT(system_charset_info != NULL);

  if (my_hash_init(&system_variable_hash, system_charset_info, 100, 0,
                   0, (my_hash_get_key) get_sys_var_length, 0, HASH_UNIQUE))
    goto error;

  if (mysql_add_sys_var_chain(all_sys_vars.first))
    goto error;

  DBUG_RETURN(0);

error:
  fprintf(stderr, "failed to initialize System variables");
  DBUG_RETURN(1);
}

int sys_var_add_options(std::vector<my_option> *long_options, int parse_flags)
{
  DBUG_ENTER("sys_var_add_options");

  for (sys_var *var=all_sys_vars.first; var; var= var->next)
  {
    if (var->register_option(long_options, parse_flags))
      goto error;
  }

  DBUG_RETURN(0);

error:
  fprintf(stderr, "failed to initialize System variables");
  DBUG_RETURN(1);
}

void sys_var_end()
{
  DBUG_ENTER("sys_var_end");

  my_hash_free(&system_variable_hash);

  for (sys_var *var=all_sys_vars.first; var; var= var->next)
    var->cleanup();

  DBUG_VOID_RETURN;
}

/**
  sys_var constructor

  @param chain     variables are linked into chain for mysql_add_sys_var_chain()
  @param name_arg  the name of the variable. Must be 0-terminated and exist
                   for the liftime of the sys_var object. @sa my_option::name
  @param comment   shown in mysqld --help, @sa my_option::comment
  @param flags_arg or'ed flag_enum values
  @param off       offset of the global variable value from the
                   &global_system_variables.
  @param getopt_id -1 for no command-line option, otherwise @sa my_option::id
  @param getopt_arg_type @sa my_option::arg_type
  @param show_val_type_arg what value_ptr() returns for sql_show.cc
  @param def_val   default value, @sa my_option::def_value
  @param lock      mutex or rw_lock that protects the global variable
                   *in addition* to LOCK_global_system_variables.
  @param binlog_status_enum @sa binlog_status_enum
  @param on_check_func a function to be called at the end of sys_var::check,
                   put your additional checks here
  @param on_update_func a function to be called at the end of sys_var::update,
                   any post-update activity should happen here
  @param substitute If non-NULL, this variable is deprecated and the
  string describes what one should use instead. If an empty string,
  the variable is deprecated but no replacement is offered.
  @param parse_flag either PARSE_EARLY or PARSE_NORMAL
*/
sys_var::sys_var(sys_var_chain *chain, const char *name_arg,
                 const char *comment, int flags_arg, ptrdiff_t off,
                 int getopt_id, enum get_opt_arg_type getopt_arg_type,
                 SHOW_TYPE show_val_type_arg, longlong def_val,
                 PolyLock *lock, enum binlog_status_enum binlog_status_arg,
                 on_check_function on_check_func,
                 on_update_function on_update_func,
                 const char *substitute, int parse_flag) :
  next(0),
  binlog_status(binlog_status_arg),
  flags(flags_arg), m_parse_flag(parse_flag), show_val_type(show_val_type_arg),
  guard(lock), offset(off), on_check(on_check_func), on_update(on_update_func),
  deprecation_substitute(substitute),
  is_os_charset(FALSE)
{
  /*
    There is a limitation in handle_options() related to short options:
    - either all short options should be declared when parsing in multiple stages,
    - or none should be declared.
    Because a lot of short options are used in the normal parsing phase
    for mysqld, we enforce here that no short option is present
    in the first (PARSE_EARLY) stage.
    See handle_options() for details.
  */
  DBUG_ASSERT(parse_flag == PARSE_NORMAL || getopt_id <= 0 || getopt_id >= 255);

  name.str= name_arg;     // ER_NO_DEFAULT relies on 0-termination of name_arg
  name.length= strlen(name_arg);                // and so does this.
  DBUG_ASSERT(name.length <= NAME_CHAR_LEN);

  memset(&option, 0, sizeof(option));
  option.name= name_arg;
  option.id= getopt_id;
  option.comment= comment;
  option.arg_type= getopt_arg_type;
  option.value= (uchar **)global_var_ptr();
  option.def_value= def_val;

  if (chain->last)
    chain->last->next= this;
  else
    chain->first= this;
  chain->last= this;
}

bool sys_var::update(THD *thd, set_var *var)
{
  enum_var_type type= var->type;
  if (type == OPT_GLOBAL || scope() == GLOBAL)
  {
    /*
      Yes, both locks need to be taken before an update, just as
      both are taken to get a value. If we'll take only 'guard' here,
      then value_ptr() for strings won't be safe in SHOW VARIABLES anymore,
      to make it safe we'll need value_ptr_unlock().
    */
    AutoWLock lock1(&PLock_global_system_variables);
    AutoWLock lock2(guard);
    return global_update(thd, var) ||
      (on_update && on_update(this, thd, OPT_GLOBAL));
  }
  else
    return session_update(thd, var) ||
      (on_update && on_update(this, thd, OPT_SESSION));
}

uchar *sys_var::session_value_ptr(THD *thd, LEX_STRING *base)
{
  return session_var_ptr(thd);
}

uchar *sys_var::global_value_ptr(THD *thd, LEX_STRING *base)
{
  return global_var_ptr();
}

uchar *sys_var::value_ptr(THD *thd, enum_var_type type, LEX_STRING *base)
{
  if (type == OPT_GLOBAL || scope() == GLOBAL)
  {
    mysql_mutex_assert_owner(&LOCK_global_system_variables);
    AutoRLock lock(guard);
    return global_value_ptr(thd, base);
  }
  else
    return session_value_ptr(thd, base);
}

bool sys_var::set_default(THD *thd, set_var* var)
{
  if (var->type == OPT_GLOBAL || scope() == GLOBAL)
    global_save_default(thd, var);
  else
    session_save_default(thd, var);

  return update(thd, var);
}

void sys_var::do_deprecated_warning(THD *thd)
{
  if (deprecation_substitute != NULL)
  {
    char buf1[NAME_CHAR_LEN + 3];
    strxnmov(buf1, sizeof(buf1)-1, "@@", name.str, 0);

    /* 
       if deprecation_substitute is an empty string,
       there is no replacement for the syntax
    */
    uint errmsg= deprecation_substitute[0] == '\0'
      ? ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT
      : ER_WARN_DEPRECATED_SYNTAX;
    if (thd)
      push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
                          ER_WARN_DEPRECATED_SYNTAX, ER(errmsg),
                          buf1, deprecation_substitute);
    else
      sql_print_warning(ER_DEFAULT(errmsg), buf1, deprecation_substitute);
  }
}

/**
  Throw warning (error in STRICT mode) if value for variable needed bounding.
  Plug-in interface also uses this.

  @param thd         thread handle
  @param name        variable's name
  @param fixed       did we have to correct the value? (throw warn/err if so)
  @param is_unsigned is value's type unsigned?
  @param v           variable's value

  @retval         true on error, false otherwise (warning or ok)
 */
bool throw_bounds_warning(THD *thd, const char *name,
                          bool fixed, bool is_unsigned, longlong v)
{
  if (fixed)
  {
    char buf[22];

    if (is_unsigned)
      ullstr((ulonglong) v, buf);
    else
      llstr(v, buf);

    if (thd->variables.sql_mode & MODE_STRICT_ALL_TABLES)
    {
      my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name, buf);
      return true;
    }
    push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
                        ER_TRUNCATED_WRONG_VALUE,
                        ER(ER_TRUNCATED_WRONG_VALUE), name, buf);
  }
  return false;
}

bool throw_bounds_warning(THD *thd, const char *name, bool fixed, double v)
{
  if (fixed)
  {
    char buf[64];

    my_gcvt(v, MY_GCVT_ARG_DOUBLE, sizeof(buf) - 1, buf, NULL);

    if (thd->variables.sql_mode & MODE_STRICT_ALL_TABLES)
    {
      my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), name, buf);
      return true;
    }
    push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
                        ER_TRUNCATED_WRONG_VALUE,
                        ER(ER_TRUNCATED_WRONG_VALUE), name, buf);
  }
  return false;
}

const CHARSET_INFO *sys_var::charset(THD *thd)
{
  return is_os_charset ? thd->variables.character_set_filesystem :
    system_charset_info;
}

typedef struct old_names_map_st
{
  const char *old_name;
  const char *new_name;
} my_old_conv;

static my_old_conv old_conv[]=
{
  {     "cp1251_koi8"           ,       "cp1251"        },
  {     "cp1250_latin2"         ,       "cp1250"        },
  {     "kam_latin2"            ,       "keybcs2"       },
  {     "mac_latin2"            ,       "MacRoman"      },
  {     "macce_latin2"          ,       "MacCE"         },
  {     "pc2_latin2"            ,       "pclatin2"      },
  {     "vga_latin2"            ,       "pclatin1"      },
  {     "koi8_cp1251"           ,       "koi8r"         },
  {     "win1251ukr_koi8_ukr"   ,       "win1251ukr"    },
  {     "koi8_ukr_win1251ukr"   ,       "koi8u"         },
  {     NULL                    ,       NULL            }
};

const CHARSET_INFO *get_old_charset_by_name(const char *name)
{
  my_old_conv *conv;

  for (conv= old_conv; conv->old_name; conv++)
  {
    if (!my_strcasecmp(&my_charset_latin1, name, conv->old_name))
      return get_charset_by_csname(conv->new_name, MY_CS_PRIMARY, MYF(0));
  }
  return NULL;
}

/****************************************************************************
  Main handling of variables:
  - Initialisation
  - Searching during parsing
  - Update loop
****************************************************************************/

/**
  Add variables to the dynamic hash of system variables

  @param first       Pointer to first system variable to add

  @retval
    0           SUCCESS
  @retval
    otherwise   FAILURE
*/


int mysql_add_sys_var_chain(sys_var *first)
{
  sys_var *var;

  /* A write lock should be held on LOCK_system_variables_hash */

  for (var= first; var; var= var->next)
  {
    /* this fails if there is a conflicting variable name. see HASH_UNIQUE */
    if (my_hash_insert(&system_variable_hash, (uchar*) var))
    {
      fprintf(stderr, "*** duplicate variable name '%s' ?\n", var->name.str);
      goto error;
    }
  }
  return 0;

error:
  for (; first != var; first= first->next)
    my_hash_delete(&system_variable_hash, (uchar*) first);
  return 1;
}


/*
  Remove variables to the dynamic hash of system variables

  SYNOPSIS
    mysql_del_sys_var_chain()
    first       Pointer to first system variable to remove

  RETURN VALUES
    0           SUCCESS
    otherwise   FAILURE
*/

int mysql_del_sys_var_chain(sys_var *first)
{
  int result= 0;

  /* A write lock should be held on LOCK_system_variables_hash */

  for (sys_var *var= first; var; var= var->next)
    result|= my_hash_delete(&system_variable_hash, (uchar*) var);

  return result;
}


static int show_cmp(SHOW_VAR *a, SHOW_VAR *b)
{
  return strcmp(a->name, b->name);
}


/**
  Constructs an array of system variables for display to the user.

  @param thd       current thread
  @param sorted    If TRUE, the system variables should be sorted
  @param type      OPT_GLOBAL or OPT_SESSION for SHOW GLOBAL|SESSION VARIABLES

  @retval
    pointer     Array of SHOW_VAR elements for display
  @retval
    NULL        FAILURE
*/

SHOW_VAR* enumerate_sys_vars(THD *thd, bool sorted, enum enum_var_type type)
{
  int count= system_variable_hash.records, i;
  int size= sizeof(SHOW_VAR) * (count + 1);
  SHOW_VAR *result= (SHOW_VAR*) thd->alloc(size);

  if (result)
  {
    SHOW_VAR *show= result;

    for (i= 0; i < count; i++)
    {
      sys_var *var= (sys_var*) my_hash_element(&system_variable_hash, i);
      const my_bool *hidden=
        getopt_constraint_get_hidden_value(var->name.str, var->name.length,
                                           FALSE);

      if (hidden && *hidden)
        continue;

      // don't show session-only variables in SHOW GLOBAL VARIABLES
      if (type == OPT_GLOBAL && var->check_type(type))
        continue;

      /* don't show non-visible variables */
      if (var->not_visible())
        continue;

      show->name= var->name.str;
      show->value= (char*) var;
      show->type= SHOW_SYS;
      show++;
    }

    /* sort into order */
    if (sorted)
      my_qsort(result, show-result, sizeof(SHOW_VAR),
               (qsort_cmp) show_cmp);

    /* make last element empty */
    memset(show, 0, sizeof(SHOW_VAR));
  }
  return result;
}

/**
  Find a user set-table variable.

  @param str       Name of system variable to find
  @param length    Length of variable.  zero means that we should use strlen()
                   on the variable

  @retval
    pointer     pointer to variable definitions
  @retval
    0           Unknown variable (error message is given)
*/

sys_var *intern_find_sys_var(const char *str, uint length)
{
  sys_var *var;

  /*
    This function is only called from the sql_plugin.cc.
    A lock on LOCK_system_variable_hash should be held
  */
  var= (sys_var*) my_hash_search(&system_variable_hash,
                              (uchar*) str, length ? length : strlen(str));

  /* Don't show non-visible variables. */
  if (var && var->not_visible())
    return NULL;

  return var;
}

/**
  Update variable

  @param   thd    thread handler
  @returns 0|1    ok or ERROR

  @note ERROR can be only due to abnormal operations involving
  the server's execution evironment such as
  out of memory, hard disk failure or the computer blows up.
  Consider set_var::check() method if there is a need to return
  an error due to logics.
*/
int set_var::update(THD *thd)
{
  return value ? var->update(thd, this) : var->set_default(thd, this);
}

/**
  Self-print assignment

  @param   str    string buffer to append the partial assignment to
*/
void set_var::print(THD *thd, String *str)
{
  str->append(type == OPT_GLOBAL ? "GLOBAL " : "SESSION ");
  if (base.length)
  {
    str->append(base.str, base.length);
    str->append(STRING_WITH_LEN("."));
  }
  str->append(var->name.str,var->name.length);
  str->append(STRING_WITH_LEN("="));
  if (value)
    value->print(str, QT_ORDINARY);
  else
    str->append(STRING_WITH_LEN("DEFAULT"));
}


void set_var_user::print(THD *thd, String *str)
{
  user_var_item->print_assignment(str, QT_ORDINARY);
}


/*****************************************************************************
  Functions to handle SET PASSWORD
*****************************************************************************/
void set_var_password::print(THD *thd, String *str)
{
  if (user->user.str != NULL && user->user.length > 0)
  {
    str->append(STRING_WITH_LEN("PASSWORD FOR "));
    append_identifier(thd, str, user->user.str, user->user.length);
    if (user->host.str != NULL && user->host.length > 0)
    {
      str->append(STRING_WITH_LEN("@"));
      append_identifier(thd, str, user->host.str, user->host.length);
    }
    str->append(STRING_WITH_LEN("="));
  }
  else
    str->append(STRING_WITH_LEN("PASSWORD FOR CURRENT_USER()="));
  str->append(STRING_WITH_LEN("<secret>"));
}

/*****************************************************************************
  Functions to handle SET NAMES and SET CHARACTER SET
*****************************************************************************/

int set_var_collation_client::update(THD *thd)
{
  thd->variables.character_set_client= character_set_client;
  thd->variables.character_set_results= character_set_results;
  thd->variables.collation_connection= collation_connection;
  thd->update_charset();
  return 0;
}

void set_var_collation_client::print(THD *thd, String *str)
{
  str->append((set_cs_flags & SET_CS_NAMES) ? "NAMES " : "CHARACTER SET ");
  if (set_cs_flags & SET_CS_DEFAULT)
    str->append("DEFAULT");
  else
  {
    str->append("'");
    str->append(character_set_client->csname);
    str->append("'");
    if (set_cs_flags & SET_CS_COLLATE)
    {
      str->append(" COLLATE '");
      str->append(collation_connection->name);
      str->append("'");
    }
  }
}
